﻿namespace VirtualTemplates
{
    using System;
    using System.Collections;
    using System.Diagnostics.Contracts;
    using System.Globalization;
    using System.IO;
    using System.Web;
    using System.Web.Caching;
    using System.Web.Hosting;

    /// <summary>
    /// A virtual template that maps the virtual pathes to physical pathes on the file system.
    /// </summary>
    public class MappedVirtualTemplate : IVirtualTemplate
    {
        private readonly string templatePath;

        /// <summary>
        /// Initializes a new instance of the <see cref="MappedVirtualTemplate"/> class.
        /// </summary>
        /// <param name="templatePath">The template path.</param>
        public MappedVirtualTemplate(string templatePath)
        {
            Contract.Requires(templatePath != null);

            this.templatePath = templatePath;
        }

        /// <summary>
        /// Gets a value that indicates whether a file exists in the virtual file system.
        /// </summary>
        /// <param name="virtualPath">The path to the virtual file.</param>
        /// <returns>
        /// true if the file exists in the virtual file system; otherwise, false.
        /// </returns>
        public bool FileExists(string virtualPath)
        {
            return File.Exists(this.PhysicalPathFor(virtualPath));
        }

        /// <summary>
        /// Gets a virtual file from the virtual file system.
        /// </summary>
        /// <param name="virtualPath">The path to the virtual file.</param>
        /// <returns>
        /// A descendent of the <see cref="T:System.Web.Hosting.VirtualFile"/> class that represents a file in the virtual file system.
        /// </returns>
        public VirtualFile GetFile(string virtualPath)
        {
            return this.FileExists(virtualPath) 
                ? new MappedVirtualFile(new FileInfo(this.PhysicalPathFor(virtualPath)), virtualPath) 
                : null;
        }

        /// <summary>
        /// Gets a value that indicates whether a directory exists in the virtual file system.
        /// </summary>
        /// <param name="virtualDir">The path to the virtual directory.</param>
        /// <returns>
        /// true if the directory exists in the virtual file system; otherwise, false.
        /// </returns>
        public bool DirectoryExists(string virtualDir)
        {
            return Directory.Exists(this.PhysicalPathFor(virtualDir));
        }

        /// <summary>
        /// Gets a virtual diretory from the virtual file system.
        /// </summary>
        /// <param name="virtualDir">The path to the virtual directory.</param>
        /// <returns>
        /// A descendent of the <see cref="T:System.Web.Hosting.VirtualDirectory"/> class that represents a directory in the virtual file system.
        /// </returns>
        public VirtualDirectory GetDirectory(string virtualDir)
        {
            return new MappedVirtualDirectory(new DirectoryInfo(this.PhysicalPathFor(virtualDir)), virtualDir);
        }

        /// <summary>
        /// Creates a cache dependency based on the specified virtual paths.
        /// </summary>
        /// <param name="virtualPath">The path to the primary virtual resource.</param>
        /// <param name="virtualPathDependencies">An array of paths to other resources required by the primary virtual resource.</param>
        /// <param name="utcStart">The UTC time at which the virtual resources were read.</param>
        /// <returns>
        /// A <see cref="T:System.Web.Caching.CacheDependency"/> object for the specified virtual resources.
        /// </returns>
        public CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            if (this.FileExists(virtualPath) || this.DirectoryExists(virtualPath))
            {
                return new CacheDependency(this.PhysicalPathFor(virtualPath), utcStart);
            }

            return null;
        }

        /// <summary>
        /// Returns a hash of the specified virtual paths.
        /// </summary>
        /// <param name="virtualPath">The path to the primary virtual resource.</param>
        /// <param name="virtualPathDependencies">An array of paths to other virtual resources required by the primary virtual resource.</param>
        /// <returns>
        /// A hash of the specified virtual paths.
        /// </returns>
        public string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
        {
            var hash = new HashCode();

            foreach (var virtualPathDependency in virtualPathDependencies)
            {
                var fileInfo = new FileInfo(this.PhysicalPathFor((string)virtualPathDependency));

                hash.Add(fileInfo.FullName.GetHashCode());
                hash.Add(fileInfo.LastWriteTimeUtc.GetHashCode());
            }

            return hash.ToString();
        }

        /// <summary>
        /// Gets the physical path for a specified virtual path.
        /// </summary>
        /// <param name="virtualPath">The virtual path.</param>
        /// <returns>The physical path.</returns>
        private string PhysicalPathFor(string virtualPath)
        {
            var relativePath = VirtualPathUtility.MakeRelative("~/", virtualPath).Replace('/', '\\');
            return Path.Combine(this.templatePath, relativePath);
        }
    }
}
